// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;

namespace System.Xml
{
    internal abstract partial class Base64Encoder
    {
        internal abstract Task WriteCharsAsync(char[] chars, int index, int count);

        internal Task EncodeAsync(byte[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
            if (index < 0 || (uint)count > buffer.Length - index)
            {
                throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count));
            }
            return Core(buffer, index, count);

            async Task Core(byte[] buffer, int index, int count)
            {
                // encode left-over buffer
                if (_leftOverBytesCount > 0)
                {
                    int i = _leftOverBytesCount;
                    while (i < 3 && count > 0)
                    {
                        _leftOverBytes![i++] = buffer[index++];
                        count--;
                    }

                    // the total number of buffer we have is less than 3 -> return
                    if (count == 0 && i < 3)
                    {
                        _leftOverBytesCount = i;
                        return;
                    }

                    // encode the left-over buffer and write out
                    int leftOverChars = Convert.ToBase64CharArray(_leftOverBytes!, 0, 3, _charsLine, 0);
                    await WriteCharsAsync(_charsLine, 0, leftOverChars).ConfigureAwait(false);
                }

                // store new left-over buffer
                _leftOverBytesCount = count % 3;
                if (_leftOverBytesCount > 0)
                {
                    count -= _leftOverBytesCount;
                    _leftOverBytes ??= new byte[3];
                    for (int i = 0; i < _leftOverBytesCount; i++)
                    {
                        _leftOverBytes[i] = buffer[index + count + i];
                    }
                }

                // encode buffer in 76 character long chunks
                int endIndex = index + count;
                int chunkSize = LineSizeInBytes;
                while (index < endIndex)
                {
                    if (index + chunkSize > endIndex)
                    {
                        chunkSize = endIndex - index;
                    }
                    int charCount = Convert.ToBase64CharArray(buffer, index, chunkSize, _charsLine, 0);
                    await WriteCharsAsync(_charsLine, 0, charCount).ConfigureAwait(false);

                    index += chunkSize;
                }
            }
        }

        internal async Task FlushAsync()
        {
            if (_leftOverBytesCount > 0)
            {
                int leftOverChars = Convert.ToBase64CharArray(_leftOverBytes!, 0, _leftOverBytesCount, _charsLine, 0);
                await WriteCharsAsync(_charsLine, 0, leftOverChars).ConfigureAwait(false);
                _leftOverBytesCount = 0;
            }
        }
    }

    internal sealed partial class XmlTextWriterBase64Encoder : Base64Encoder
    {
        internal override Task WriteCharsAsync(char[] chars, int index, int count)
        {
            throw new NotImplementedException();
        }
    }

    internal sealed partial class XmlRawWriterBase64Encoder : Base64Encoder
    {
        internal override Task WriteCharsAsync(char[] chars, int index, int count)
        {
            return _rawWriter.WriteRawAsync(chars, index, count);
        }
    }
}
